package learn_struct

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// struct 的 tag 属性
type person struct {
	Name string `json:"name"`
	Age  int    `json:"age" valid:"1-100"`
}

//当golang的对象需要和json做转换的时候，我们就经常用到这个特性

func Test() {
	p := person{Name: "jj", Age: 20}
	if b, err := json.Marshal(p); err != nil {
		return
	} else {
		fmt.Printf("%s\n", b)
	}
	p.validate()
}

// 如何使用 tag 属性，字段标记
func (p *person) validate() bool {
	v := reflect.ValueOf(*p)
	tag := v.Type().Field(1).Tag.Get("valid")
	val := v.Field(1).Interface().(int)
	fmt.Printf("tag=%v, val=%v\n", tag, val)
	result := strings.Split(tag, "-")
	var min, max int
	min, _ = strconv.Atoi(result[0])
	max, _ = strconv.Atoi(result[1])

	if val >= min && val <= max {
		return true
	} else {
		return false
	}
}

// 用户自定义类型。定义结构体，它代表若干字段的集合

// 1. 结构体由一系列命名的元素组成，这些元素又被称为字段，每个字段都有一个名称和一个类型。
// 标识果一个字段在代码中从来不会被用到，那可以把它命名为 _，即空符。
type File struct {
	Name     string
	Size     int64
	CreateAt string
	UpdateAt string

	// 两个匿名字段
	string
	int
	_ string
}

// 简单写法
type T struct {
	// 结构体中 大写开头才可能被外部访问到，被 import 时才可以被访问
	A, B string
}

// 2. 结构体中的字段可以是任何类型，甚至是结构体本身，也可以是函数或者接口

// 使用结构体，一旦定义了结构体类型就可以使用这个类型创建值。
// 使用结构类型声明变量，并初始化为零值
// 任何时候，创建一个变量并初始化为零值，习惯上会使用关键字 var。
// 这种用法是为了更明确地表示一个变量被设置为零值。
var f File

func init() {
	f.Name = "巴黎烧了吗"

	// 使用结构体字面量声明变量，并初始化为非零值
	f1 := File{
		Name: string("西游记"),
	}
	//f2 := File{"东游记", 76, "createAt", "updateAt", string("1"), 1}
	fmt.Println(f1)
	testNew()
}

// 注意：数组可以看作是一种结构体类型，不过它使用下标而不是具名的字段

// 3. 字段标记
type FileMeta struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

// 访问结构体成员需要使用点号操作符，点号操作符也被称为选择器(selector)
type test struct {
	name string
	age  int
	int  // 匿名字段
}

// 内嵌结构体
type Person struct {
	Name  string
	Age   int
	Email string
}
type Student struct {
	*Person
	StudentID int
}

func initStudent() {
	stu := Student{
		Person: &Person{
			"hh", 2, "ss",
		},
		StudentID: 2222,
	}
	fmt.Printf("%#v", stu)
}

// 4. 结构体方法
// 对象方法在结构体定义的外部添加
// func关键字后面的(stu *Student)，
// 表示该函数传入一个指向 Student 的指针，
// 可通过指针变量 stu 来操作结构体的值。
func (stu *Student) GetStudent() Person {
	p := &Person{
		Name: stu.Name,
	}
	return *p
}

/*
一般在进行例如 type T struct {a, b int} 的结构体定义之后
习惯使用t := new(T)给该结构体变量分配内存，它返回指向已分配内存的指针。
变量t是一个指向T的指针，此时结构体字段的值是它们所属类型的零值。
声明 var t1 T 也会给 t1 分配内存，并零值化内存，但是这个时候 t1 是类型 T。
在这两种方式中，t1 通常被称做类型T的一个实例（instance）或对象（Object）。
var t1 *T = new(T)等价于t := new(T)。
*/
func testNew() {
	var tt *T
	t3 := T{}
	tt = &t3 // 通过&操作符取 t3 的内存地址，地址保存到指针 tt 中

	fmt.Print(tt)

	fmt.Println("=====test-new-func====")
	t := new(T)        // 会返回一个指针地址
	var t1 *T = new(T) // 指针类型变量（结构体指针） *T 类似于 *int *string
	fmt.Println(t)
	fmt.Println(t1)
	fmt.Println(*t)
}

// 5. 给结构体定义方法
func (file File) GetFile(a string) File {
	var i *int
	var ii = 10
	i = &ii
	fmt.Println(i)
	f := &file
	return *f
}
